fix(tracing): continue inbound W3C trace context at the ACP boundary - #490
Open
NiteshDhanpal wants to merge 2 commits into
Open
fix(tracing): continue inbound W3C trace context at the ACP boundary#490NiteshDhanpal wants to merge 2 commits into
NiteshDhanpal wants to merge 2 commits into
Conversation
NiteshDhanpal
force-pushed
the
fix/acp-w3c-ingress-trace-context
branch
from
August 6, 2026 21:44
f780475 to
a10bd94
Compare
stainless-app
Bot
force-pushed
the
next
branch
from
August 10, 2026 15:24
462195d to
7ff83aa
Compare
Root cause of async trace detachment (proven via [TP-DEBUG] probes): the ingress traceparent arrives in the HTTP header (inbound=00-<trace>...) but FastACP never extracts it, so the app's active OTel context is <none>. Downstream the Temporal start_workflow/signal (incl. the asyncio.create_task background dispatch) fires with no active span, the interceptor injects nothing, and the workflow + every activity start FRESH traces disconnected from the ingress. Extract + attach the inbound W3C context in the ASGI RequestIDMiddleware (wraps the whole request, so the bg task inherits it via create_task's context copy). Now the interceptor propagates the ingress trace across the Temporal boundary and the workflow/activity inherit it -> one connected trace. Fail-open. Unit-tested. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
NiteshDhanpal
force-pushed
the
fix/acp-w3c-ingress-trace-context
branch
from
August 10, 2026 23:36
a10bd94 to
ddb745d
Compare
…+ repeated-header support - Use TraceContextTextMapPropagator().extract() instead of the ambient global propagator, so OTEL_PROPAGATORS=datadog (plausible in a DD shop; dd_only is the default mode) can't silently disable W3C extraction. - Build a dict-of-lists carrier so repeated `tracestate` header lines are combined (W3C/RFC7230 MUST) instead of collapsing to the last value. - Parse only traceparent/tracestate, so arbitrary inbound `baggage` isn't pulled into the downstream context. Addresses the review comments on repeated-header collapse, ambient-propagator dependence, and the implicit trust boundary. `traceparent` is single-valued, so trace linkage (this PR's purpose) is unchanged. Tests: repeated tracestate combined; inbound baggage not extracted. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Targets
next, and builds on the merged #484 (business↔obs correlation) and #485 (Temporal interceptor) — this is a single logical change, no longer stacked.Problem
After #484 (business↔obs correlation + Option A) and #485 (Temporal interceptor), an async turn's obs trace still detached from the ingress: the workflow + every activity started fresh traces, unlinked from the
event/sendAPI call.Root cause (proven on infra-staging with per-hop
traceparentprobes): the ingresstraceparentarrives at the agent's ACP server in the HTTP header, but FastACP never extracts it — the active OTel context stays empty. Sostart_workflow/signal(and theasyncio.create_taskbackground dispatch) fire with no active span, the Temporal interceptor injects nothing, and the workflow/activities root fresh traces.Fix
Extract + attach the inbound W3C context in the ASGI
RequestIDMiddleware, which wraps the whole request (so the background task inherits it viacreate_task's context copy). Now the interceptor propagates the ingress trace across the Temporal boundary and the workflow/activity inherit it → one connected trace. Fail-open (obs never breaks a request).Parsing is hardened: it uses the explicit
TraceContextTextMapPropagator(immune toOTEL_PROPAGATORS=datadogsilently disabling W3C), combines repeatedtracestateheader lines (W3C/RFC7230 MUST) instead of collapsing to the last, and excludes inboundbaggageso attacker-controlled keys aren't pulled into downstream context.traceparentis single-valued, so trace linkage is unchanged.Verified (infra-staging)
Trace
12c6290e…: 347 spans across all 4 services in one tree — gatewayPOST /agents/{id}/rpc→ agent ACP →RunWorkflow→HandleSignal:receive_event→RunActivity→ egp/identity/DB. Per-hop probes confirmedtraceparentwent from<none>→ the ingress trace at every hop (ACP + worker). (Requires the control-plane header-forward fix, scaleapi/scale-agentex#396, to deliver the header.)Tests
tests/test_trace_context_extraction.py— extract-makes-inbound-active, fail-open on missing header, safe detach, repeatedtracestatecombined, and inboundbaggagenot extracted.ruffclean.🤖 Generated with Claude Code
Greptile Summary
The PR continues inbound W3C trace context at the ACP request boundary so asynchronous Temporal dispatch inherits the ingress trace.
traceparentandtracestate.tracestateheader values while excluding inbound baggage.Confidence Score: 5/5
The PR appears safe to merge.
No blocking failure remains.
Important Files Changed
Sequence Diagram
sequenceDiagram participant Gateway participant ACP as ACP Middleware participant Handler participant Temporal Gateway->>ACP: HTTP request with traceparent/tracestate ACP->>ACP: Extract and attach W3C context ACP->>Handler: Run request under ingress context Handler->>Temporal: Start workflow or signal Note over Handler,Temporal: Async dispatch inherits attached context Temporal-->>Handler: Dispatch accepted Handler-->>ACP: Response ACP->>ACP: Detach context safely ACP-->>Gateway: HTTP responseReviews (4): Last reviewed commit: "fix(tracing): parse ACP ingress headers ..." | Re-trigger Greptile